package net.penguinmenac3.math;

/**
 * An implementation of the gausian algorithm.
 * 
 * @author Michael
 */
public class Gausian {
	private Matrix m;

	/**
	 * The matrix that should be used for the calculations.
	 * 
	 * @param m
	 */
	public Gausian(Matrix m) {
		if (m == null) {
			throw new NullPointerException();
		}
		this.m = m;
	}

	/**
	 * Solve a matrix. Calls solve(0, false).
	 * 
	 * @return
	 */
	public Matrix solve() {
		return solve(0, false);
	}

	/**
	 * Calculates the gausian form.
	 * 
	 * @param verbose
	 *            0 := none, 1 := steps, 2 := what calculations
	 * @param stepFormOnly
	 *            Do you only want the step form not a complete gausian form.
	 * @return
	 */
	public Matrix solve(int verbose, boolean stepFormOnly) {
		if (verbose > 0) {
			System.out.println("Solving:");
			System.out.println(m.toString());
		}
		int nonNullRows = 0;

		// Treppenform
		for (int step = 0; step < m.getColumnCount() && step < m.getRowCount(); step++) {
			m.sort();
			Vector v1 = new Vector(m.getRow(step));
			if (v1.isNull()) {
				break;
			}
			if (verbose > 0) {
				System.out.println("Step " + step);
			}
			nonNullRows++;
			int first = v1.getFirstIndexOfNonNullElement();

			if (verbose > 1) {
				System.out.println("Divide row" + (step + 1) + " by "
						+ m.getRow(step)[first]);
			}
			m.divideRow(step, m.getRow(step)[first]);

			for (int row = step + 1; row < m.getRowCount(); row++) {
				Vector v2 = new Vector(m.getRow(row));
				if (v2.isNull()) {
					continue;
				}
				if (first != v2.getFirstIndexOfNonNullElement()) {
					continue;
				}
				if (!m.getRow(row)[first].isNeutralElement()) {
					if (verbose > 1) {
						System.out.println("Divide row" + (row + 1) + " by "
								+ m.getRow(row)[first]);
					}
					m.divideRow(row, m.getRow(row)[first]);
				}
				if (verbose > 1) {
					System.out.println("Subtract row" + (step + 1)
							+ " from row" + (row + 1));
				}
				m.subtractRows(row, step);
			}
			if (verbose > 0) {
				System.out.println(m.toString());
			}
		}
		if (stepFormOnly) {
			return m;
		}

		// Eliminiere Zahlen über einsern
		for (int step = 0; step < nonNullRows - 1; step++) {
			int currentRow = m.getRowCount() - step
					- (m.getRowCount() - nonNullRows) - 1;
			if (verbose > 0) {
				System.out.println("Step " + (step + nonNullRows));
			}
			Vector v1 = new Vector(m.getRow(currentRow));
			int first = v1.getFirstIndexOfNonNullElement();
			if (first == Integer.MAX_VALUE) {
				// There is an undetected null row.
				continue;
			}

			for (int row = 0; row < currentRow; row++) {
				Vector v2 = new Vector(m.getRow(row));
				MathObject scalar = v2.toArray()[first];
				if (scalar.isNullElement()) {
					continue;
				}
				if (verbose > 1) {
					System.out.println("Subtract " + scalar + " * row"
							+ (currentRow + 1) + " from row" + (row + 1));
				}
				m.multiplyRow(currentRow, scalar);
				m.subtractRows(row, currentRow);
				m.divideRow(currentRow, scalar);
			}
			if (verbose > 0) {
				System.out.println(m.toString());
			}
		}
		return m;
	}
}
